Assembler für Einsteiger

← Zurück zum Forum

Hallo in die Runde, auf der Fujiyama wollte ich eigentlich viel mehr Assembler programmieren und lernen... leider war zu wenig Zeit und zu viele andere wichtige Dinge. Vielleicht hat jemand Lust hier auf dem Forum ein wenig mit mir zu lernen... da bin ich ja auch noch ganz blutiger Anfänger und mache jetzt erste Schritte!

Mein erster Schritt war natürlich eine "Hallo Welt!" Routine zu schreiben. Ich wollte dabei wirklich ohne die vorhandenen I/O-Routinen des Betriebssytems das selber auf den Bildschirm printen. Das ist zunächst relativ einfach, wenn man weiß, wo der Bildschirm anfängt: 40000 - bzw. in den Speicherstellen 88/89. Nun wollte ich es aber so haben, daß das Programm 15 x "Hallo Welt!" untereinander (nicht nebeneinander) auf den Bildschirm printet. Eine nicht ganz einfache Aufgabe für den Start, muss ich sagen, aber wenn man das dann löst, hat man viel gelernt!! Hier nun meine (sicherlich echt für den Profi sehr holprige) Lösung - mit dem Aufruf: Wie bekommt man das noch schlanker, kürzer und eleganter hin?? Liebe Grüße Peter

        org $4000
text .byte "Hallo Welt! "
var = 88
var2 = 89
var3 = $6000

start       lda #0
        sta var3
main        ldy #0
        ldx #0
loop1       lda text,x
        sta (var),y
        inx
        iny
        cpx #12
        bne loop1
        clc
        ldy #0
        ldx #0
        lda var
        adc #40
        sta var
        lda var2
        adc #0
        sta var2
        ldx var3
        inx
        stx var3
        cpx #15
        bne main

loop3       jmp loop3



        run start
        org $4000
text .byte "Hallo Welt! "
var = 88
var2 = 89

start       ldx #0
main        ldy #0
loop1       lda text,y
        sta (var),y
        iny
        cpy #12
        bne loop1
        clc
        ldy #0
        lda var
        adc #40
        sta var
        lda var2
        adc #0
        sta var2
        inx
        cpx #15
        bne main
loop3       jmp loop3



        run start

Drehe mal Deine loops um.

Also in der Art:

   LDY #15
LOOP
   LDA text,y
   STA (var),y
   DEY
   BPL LOOP

Man spart sich so das extra CMP #0, da solange es nicht minus ist wieder zum LOOP gesprungen wird. Achtung! Das geht natürlich nur so ganz einfach bei kleinen Loops, die nicht so oft wiederholt werden müssen, dass das High Bit von y gesetzt ist. Also maximal bis $7F.

PP´s of STARSOFTBerlin____github|meine Webseite|Demozoo

Das Programm sieht dann so aus:

        org $4000
text .byte "Hallo Welt! "
var = 88
var2 = 89
start       ldx #0
main        ldy #11
loop1       lda text,y
        sta (var),y
        dey
        bpl loop1
        clc
        lda var
        adc #40
        sta var
        lda var2
        adc #0
        sta var2
        inx
        cpx #15
        bne main
loop3       jmp loop3

        run start

... und ist tatsächlich noch schlanker... !! Supi! Ich denke mit diesem Ansatz wird es nicht viel schlanker werden.. vielleicht gibt es noch einen ganz anderen Ansatz!

Als nächstes möchte ich den Text "Hallo Welt!" von links oben Zeile für Zeile nach links unten printen lassen, also ein grobes scrollen quasi.

        org $4000
text .byte "Hallo Welt! "
SAVMSC = 88
start   ldx #14
main    ldy #11
loop1   lda text,y
    sta (var),y
    dey
    bpl loop1
    clc
    lda SAVMSC
    adc #40
    sta SAVMSC
    bcc checkEnd
    inc SAVMSC+1
checkEnd
    dex
    bpl main

loop3   jmp loop3

    run start

(Ob es eine gute Idee ist SAVMSC zu verändern, oder ob man nicht lieber eine andere Variable dafür nutzt, ist eine andere Diskussion.)

... cool ... damit ist der Übertrag natürlich deutlich eleganter gelöst und auch die 2. Schleife mit dex und bpl main! Ob man SAVMSC (was bedeutet eigentlich SAVMSC??) verändern sollt, habe ich mich auch schon gefragt, da man dadurch ja den Vector für die Bildschirmadresse verliert... immerhin ging es so und er hat sich nicht gleich festgefressen 😉

Prodehl schrieb: Ob man SAVMSC (was bedeutet eigentlich SAVMSC??) verändern sollt, habe ich mich auch schon gefragt, da man dadurch ja den Vector für die Bildschirmadresse verliert...

Die Adressen des Betriebssystems (und Hardware-Register) haben Standardnamen. Um die Lesbarkeit und Wiedererkennungswert (gerade bei Assembler) zu erhöhen, bietet es sich an diese auch zu verwenden. Die Namen kannst Du z.B. auch im Atari-Profibuch finden (SAVMSC auf Seite 12). Gab es auf der alten Abbuc Seite mal zum Download - keine Ahnung wo es hier verfügbar ist.

Weil Du nach anderen Herangehensweisen gefragt hast: Es gibt noch die Technik selbstmodifizierenden Code zu schreiben - das ist aber nichts, was ich einem Anfänger empfehlen würde. ...und noch einen Hinweis: Ab einen bestimmten Punkt muss man sich fragen ob der Code schneller oder kürzer werden soll. Geschwindigkeit und Speicherplatzbedarf sind unterschiedliche Optimierungsziele.

Viel Spaß!

Edit: Die symbolischen Namen kannst Du auch hier finden: https://www.atariarchives.org/mapping/memorymap.php

... mich hätte einfach interessiert, für was die savmsc genommen haben... ist aber egal. Ganz so sehr Anfänger bin ich ja nicht, eher Wiedereinsteiger, der das jetzt hinsichtlich Assembler hobbymäßig intensiver betreiben will. Damals habe ich schon direkt Maschinensprache gecodet, ist aber 40 Jahre her ... da habe ich auch mal mit selbstmodifizierenden Code experimentiert! Sehr spannend! Damals musste man sich alles selber beibringen, an Bücher kam man kaum ran bzw. es gab sie schlichtweg gar nicht!!

Prodehl schrieb: ... mich hätte einfach interessiert, für was die savmsc genommen haben... ist aber egal.

Ich würde mal auf "SAVe Memory SCreen" tippen.

Wenn Du Dich für selbstmodifizierenden Code interessiert, ich habe mal für ca65 ein Makro-Package geschrieben. Das erhöht die Lesbarkeit enorm.

https://cc65.github.io/doc/smc.html

eins noch zum Thema SAVMSC verändern... das Problem ist ja, daß man die indirekte Adressierung verwenden muss und die funktioniert ja nur bei page 0 - man muss also dann irgendwo im Bereich page 0 einen freien Bereich finden, wo man seine Variablen definieren kann... oder habe ich da einen Denkfehler??

Prodehl schrieb: eins noch zum Thema SAVMSC verändern... das Problem ist ja, daß man die indirekte Adressierung verwenden muss und die funktioniert ja nur bei page 0 - man muss also dann irgendwo im Bereich page 0 einen freien Bereich finden, wo man seine Variablen definieren kann... oder habe ich da einen Denkfehler??

Nein, genauso ist es. Man sucht sich einen freien Zero-Page-Bereich (z.B. ab $80 wenn man Basic und Fließkommazahlen nicht braucht) und arbeitet auf einer Kopie.

oder...:

...
        lda SAVMSC
        sta storeToScreen+1
        lda SAVMSC+1
        sta storeToScreen+2
...
        lda text, y
storeToScreen   sta $ABCD, y
...
        clc
        lda storeToScreen+1
        adc #40
        sta storeToScreen+1
        bcc checkEnd
        inc storeToScreen+2
...

Das wäre die Version mit SMC ohne zusätzlichen Page-0-Bedarf.

... jetzt verstehe ich es ... wie pfiffig!!!!

Prodehl schrieb: ... jetzt verstehe ich es ... wie pfiffig!!!!

Ja, man kann interessante Sachen machen um Zyklen, Zero-Page oder generell Speicherplatz zu sparen. In einem ROM läuft der Code dann natürlich nicht mehr!

Ein echt interessanter Ansatz! Der Code programmiert bzw. verändert sich dabei selber an der Stelle storescr 1+2, wo die Inhalte von savmsc reinkommen... $1234 ist dabei nur ein Platzhalter - dennoch erstaunlich, daß das so schön funktioniert, damit hebt man quasi die Beschränkung der indirekt indizierten Adressierung auf Page 0 auf.... habe ich das richtig verstanden? 😉 Der Code sieht dann so aus - und funktioniert!! Geil!

            org $6000
savmsc = 88
text .byte "Hallo Welt! "

start           lda savmsc
            sta storescr+1
            lda savmsc+1
            sta storescr+2
            ldx #14

main            ldy #11
loop1           lda text,y
storescr        sta $1234,y
            dey
            bpl loop1
            clc
            lda storescr+1
            adc #40
            sta storescr+1
            bcc chkend
            inc storescr+2
chkend          dex
            bpl main

lp          jmp lp          




            run start

Prodehl schrieb: Der Code programmiert bzw. verändert sich dabei selber an der Stelle storescr 1+2, wo die Inhalte von savmsc reinkommen... $1234 ist dabei nur ein Platzhalter - dennoch erstaunlich, daß das so schön funktioniert, damit hebt man quasi die Beschränkung der indirekt indizierten Adressierung auf Page 0 auf.... habe ich das richtig verstanden? 😉

Ja, ich hätte es nicht besser formulieren können. Leider hat diese Art der Programmierung heutzutage keine Bedeutung mehr. CPUs mit einem Instruktionscache, der bei solchen Modifikationen gelöscht werden müsste, haben dadurch große Leistungseinbußen. Viele Mikrocontroller besitzen zwar keinen Cache, haben aber einen Read-Only Programmspeicher der vom Datenspeicher getrennt ist. SMC ist auch aus sicherheitstechnischen Gründen bei modernen Architekturen sehr kritisch zu sehen. Deswegen wurde auch das NX-Bit bei CPUs eingeführt: https://de.wikipedia.org/wiki/NX-Bit

Aber bei der 6502 Programmierung sind schöne kniffelige Sachen möglich.

... im Rahmen der Sicherheit war diese Unterbindung sicherlich notwendig ... beim Atari natürlich heutzutage nicht relevant! Interessant, daß ich nirgends was finde zur Dokumentation dieser Addressierungs / Programmierart. Immerhin erweitert das die Programmiermöglichkeiten ganz erheblich! Gibt es noch mehr solcher interessanten und nicht dokumentierten Tricks??

Hier jetzt noch abschließend mein Listing für "Hallo Welt!" Grobscrolling vom Bildschirmanfang nach unten. Inclusive Warteschleife!

            org $6000
savmsc = 88
text .byte "Hallo Welt! "
var1 = 180
start           clc
            lda savmsc
            sta stor2+1
            adc #40
            sta stor1+1
            lda savmsc+1
            sta stor1+2
            sta stor2+2
            ldx #23
main            ldy #11
loop1           lda #0
stor2           sta 0,y
            lda text,y
stor1           sta 0,y
            dey
            bpl loop1
            clc 
            lda stor1+1
            adc #40
            sta stor1+1
            bcc chkend
            inc stor1+2
chkend          clc
            lda stor2+1
            adc #40
            sta stor2+1
            bcc chkend2
            inc stor2+2
chkend2         ldy #0
warten1         lda #0
            sta var1
warten2         lda var1
            adc #1
            sta var1
            bcc warten2
            iny
            bne warten1
            dex
            bpl main
            jmp start           

            run start

Prodehl schrieb: Interessant, daß ich nirgends was finde zur Dokumentation dieser Addressierungs / Programmierart. Immerhin erweitert das die Programmiermöglichkeiten ganz erheblich!

Ja, man liest in der Tat nicht viel darüber. Ich kann mich nicht mehr erinnern, wann ich zuerst darüber mal was gelesen habe (vielleicht in "Peter Finzels Assemblerecke"?). Die Verwendung war aber schon immer etwas "exotisch".

Prodehl schrieb: Gibt es noch mehr solcher interessanten und nicht dokumentierten Tricks??

Das ist immer die Frage was interessant ist und ob wenig bzw. schwer auffindbare Dokumentation "auch schon was ist".

Es gibt z.B. noch die undokumentierten 6502 Opcodes (inzwischen sind sie aber mehr als gut dokumentiert), die auf den NMOS 6502 Varianten das Befehlsspektrum erweitern. Z.B. "SAX": Speichert das Ergebnis von "A und X" an die angegebene Adresse. http://www.oxyron.de/html/opcodes02.html

Dann gibt es noch nette Tricksereien mit dem Stack - um z.B. flexible Parameterübergabe zu ermöglichen oder Rücksprungadressen anzupassen. "BRK" hat auch noch einiges, ungenutztes Potential.

Wenn Du 256 Bytes für eine "Identity Table" über hast, kannst Du neue, sinnvolle Instruktionen wie "tyx", "sbc x" oder "cmp y" damit bauen:

identity_table:
    .byte $00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$0f
    .byte $10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$1a,$1b,$1c,$1d,$1e,$1f
    .byte $20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$2b,$2c,$2d,$2e,$2f
    .byte $30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f
    .byte $40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f
    .byte $50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$5b,$5c,$5d,$5e,$5f
    .byte $60,$61,$62,$63,$64,$65,$66,$67,$68,$69,$6a,$6b,$6c,$6d,$6e,$6f
    .byte $70,$71,$72,$73,$74,$75,$76,$77,$78,$79,$7a,$7b,$7c,$7d,$7e,$7f
    .byte $80,$81,$82,$83,$84,$85,$86,$87,$88,$89,$8a,$8b,$8c,$8d,$8e,$8f
    .byte $90,$91,$92,$93,$94,$95,$96,$97,$98,$99,$9a,$9b,$9c,$9d,$9e,$9f
    .byte $a0,$a1,$a2,$a3,$a4,$a5,$a6,$a7,$a8,$a9,$aa,$ab,$ac,$ad,$ae,$af
    .byte $b0,$b1,$b2,$b3,$b4,$b5,$b6,$b7,$b8,$b9,$ba,$bb,$bc,$bd,$be,$bf
    .byte $c0,$c1,$c2,$c3,$c4,$c5,$c6,$c7,$c8,$c9,$ca,$cb,$cc,$cd,$ce,$cf
    .byte $d0,$d1,$d2,$d3,$d4,$d5,$d6,$d7,$d8,$d9,$da,$db,$dc,$dd,$de,$df
    .byte $e0,$e1,$e2,$e3,$e4,$e5,$e6,$e7,$e8,$e9,$ea,$eb,$ec,$ed,$ee,$ef
    .byte $f0,$f1,$f2,$f3,$f4,$f5,$f6,$f7,$f8,$f9,$fa,$fb,$fc,$fd,$fe,$ff

Die Instruktionen:

ldx identity_table,y    tyx
ldy identity_table,x    txy
and identity_table,x    and X
and identity_table,y    and Y
ora identity_table,x    ora X
ora identity_table,y    ora Y
eor identity_table,x    eor X
eor identity_table,y    eor Y
adc identity_table,x    adc X
adc identity_table,y    adc Y
sbc identity_table,x    sbc X
sbc identity_table,y    sbc Y
cmp identity_table,x    cmp X
cmp identity_table,y    cmp Y
bit identity_table+constant     bit #constant

All the instructions above take 3 bytes and 4 CPU cycles, assuming the table is page-aligned (starts at $xx00) and not on zero page. (Crossing a page boundary causes slowdown with indexed addressing modes.)

dieses (https://wiki.nesdev.org/w/index.php?title=Identity_table) und vieles andere kannst Du auf https://wiki.nesdev.org finden: https://wiki.nesdev.org/w/index.php?title=CPU_unofficial_opcodes https://wiki.nesdev.org/w/index.php?title=6502_assembly_optimisations https://wiki.nesdev.org/w/index.php?title=Synthetic_instructions https://wiki.nesdev.org/w/index.php?title=RTS_Trick

Viel Spaß!

Hallo, ich wollte auch mal meinen Source-Code in die Runde werfen 😀 * Es löscht den Schirm mittels OS (?chr$(125) wäre das in Basic) * Gibt 24 Zeilen "Hallo Welt!" auf Graphics 0 aus * Wartet per OS auf die "Anykey"-Taste. Das Programm ist mit dem atasm 1.09 baubar. Die .bank/.set 6,0 braucht es, da ich am Ende der Adresse 736 die Startadresse zuweise, sonst würde der atasm die Reihenfolge Speicher-Richtig umdrehen, was bei 736 dann wenig hilfreich ist.

Ich mag den 6502 Assembler, da ist alles so schön einfach. Allerdings wird Assembler-Code sehr schnell sehr unverständlich. Bei meiner aktuellen Idee habe ich jetzt schon das nachsehen und weiß nicht mehr wirklich wie es funktioniert, nur das es funktioniert.

Und ehrlich, nutze ich auch lieber Hochsprachen statt Assembler. Aber dazu später mehr...

LG Lars

 .BANK
 .SET 6,0

savmsc=88
ptr_screen=203
ptr_text=205

EDITRV = $E400  ; Editor
KEYBDV = $E420  ; Keyboard

  *=$4000

  ldy #125
  jsr ?EDITOR_OUT


  lda savmsc          ; ptr fuer Startadresse oben links
  sta ptr_screen
  lda savmsc+1
  sta ptr_screen+1


  lda #<text          ; ptr fuer Text
  sta ptr_text
  lda #>text
  sta ptr_text+1

; Die eigentlichen Schleifen

  ldx #24             ; Anzahl Zeilen
line_loop

  ldy #0
output_loop           ; loop zur Ausgabe des Textes
  lda (ptr_text),y
  sta (ptr_screen),y
  iny
  cpy #(TEXTEND-TEXT)
  bne output_loop

  clc
  lda ptr_screen
  adc #41             ; 40 waere die normale Screen-Breite
  sta ptr_screen
  lda ptr_screen+1
  adc #0
  sta ptr_screen+1

  dex
  bne line_loop

; Warte auf Tastendruck
  jsr ?getkey
  rts

TEXT
  .sbyte " Hallo Welt!"
TEXTEND
  .byte $ff

; wartet, bis eine Taste gedrueckt wurde, gibt diese im Akku zurueck
?Getkey
  LDA KEYBDV+5 ;simuliere
  PHA
  LDA KEYBDV+4
  PHA
  RTS

; Gibt ein Zeichen im Y-Register auf dem Editor E: aus (Graphics 0)
?EDITOR_OUT
  LDA EDITRV+7
  PHA
  LDA EDITRV+6
  PHA
  TYA
  RTS


 .BANK
 .SET 6,0
 *=736
 .WORD $4000